package javapop.framework.parser;

import java.util.ArrayList;
import java.util.List;

import javapop.framework.ParseContext;
import javapop.framework.ParseError;
import javapop.framework.ParseInput;
import javapop.framework.ParseResult;
import javapop.framework.Parser;

public class ListOf<T> extends Parser<List<T>> {
	private Parser<T> child;
	private Parser<?> first;
	private Parser<?> last;
	private Parser<?> sep;
	private int minCount;
	private int maxCount;
	private String description;
	
	public ListOf(Parser<T> child, int minCount, int maxCount) {
		first = null;
		if(child==null) {
			throw new IllegalArgumentException("Child parser is <null>");
		}
		this.child = child;
		last = null;
		sep = null;
		if(minCount>maxCount) {
			throw new IllegalArgumentException("minCount must be lower than maxCount");
		}
		if(minCount<0) {
			throw new IllegalArgumentException("minCount must be positive");
		}
		this.minCount = minCount;
		this.maxCount = maxCount;
		description = null;
	}

	
	public ListOf(Parser<T> child) {
		this(child,0, Integer.MAX_VALUE);
	}
	
	public ListOf(Parser<T> child, int minCount) {
		this(child,minCount, Integer.MAX_VALUE);
	}

	public ListOf<T> first(Parser<?> first) {
		this.first = first;
		return this;
	}
	
	public ListOf<T> sep(Parser<?> sep) {
		this.sep = sep;
		return this;
	}

	public ListOf<T> last(Parser<?> last) {
		this.last = last;
		return this;
	}

	//@Override
	public String getDescriptionOLD() {
		if(description==null) {
			StringBuilder buf = new StringBuilder();
			buf.append("ListOf");
			if(sep!=null || first!=null || last!=null || (minCount!=0 && maxCount!=Integer.MAX_VALUE)) {
				boolean started = false;
				buf.append("[");
				if(first!=null) {
					buf.append("first=");
					buf.append(first.getDescription());
					started = true;
				}
				if(sep!=null)  {
					if(started) {
						buf.append(",");
					}
					buf.append("sep=");
					buf.append(sep.getDescription());
					started = true;
				}
				if(last!=null)  {
					if(started) {
						buf.append(",");
					}
					buf.append("last=");
					buf.append(last.getDescription());
					started = true;
				}
				if(started) {
					buf.append(",min=");
					buf.append(minCount);
				}
				buf.append(",max=");
				buf.append(maxCount);
			
				buf.append("]");
			}
			buf.append("(");
			buf.append(child.getDescription());
			buf.append(")");
			description = buf.toString();
		}
		return description;
	}
	
	@Override
	public boolean isMemoized() {
		return false;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public ParseResult<List<T>> eval(ParseContext ctx, ParseInput input) {
		List<T> results = new ArrayList<T>();
		
		if(first!=null) {
			ParseResult<?> result = first.parse(ctx, input);
			if(result.isError()) {
				return (ParseResult) result;
			}
		}
		
		int count = 0;
		boolean finished = false;
		
		if(maxCount>0) {
			do {
				ParseResult<T> result = child.parse(ctx, input);
				if(result.isError()) {
					if(result.asError().isFatal()) {
						return (ParseResult) result;
					}
					if(count<minCount) {
						ParseError error = new ParseError(result.asError(),"Expecting (at least "+(minCount-count)+") more "+child.getDescription());
						return (ParseResult) error;
					} else {
						finished = true;
						continue;
					}
				}
				
				results.add(result.getResult());
				count++;
				
				if(count==maxCount) {
					finished = true;
				} else if(sep!=null ) { // need a separator ?
					ParseResult<?> sepResult = sep.parse(ctx, input);
					if(sepResult.isError()) {
						finished = true;
						continue;
					}
				}
			} while(!finished);
		
		}
		
		if(last!=null) {
			ParseResult<?> result = last.parse(ctx, input);
			if(result.isError()) {
				return (ParseResult) result;
			}
		}
	
		return buildResult(results); 
	}

	@Override
	public String getDescription() {
		StringBuilder sb = new StringBuilder();
		if(minCount == 0) {
			sb.append("(");
		}
		sb.append(child.toString());
		if(sep != null) {
			sb.append(" (");
			sb.append(sep.toString());
			sb.append(" ");
			sb.append(child.toString());
			sb.append(" )");
		}
		if(maxCount == Integer.MAX_VALUE) {
			sb.append("*");
		}
		else if(minCount > 1) {
			sb.append("^{");
			sb.append((minCount-1));
			sb.append(",");
			sb.append((maxCount-1));
			sb.append("}");
		}
		else {
			sb.append((maxCount-1));
		}
			
		
		if(minCount == 0) {
			sb.append(")?");
		}
		
		return sb.toString();
	}
}
